#include <GameEngine/Graphics/Shaders/shader_object.h>
#include <GameEngine/Core/Memory/memory_manager.h>


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

GAMEENGINE_IMPLEMENT_RTTI(GLSLShaderObject)

//! Default constructor
GLSLShaderObject::GLSLShaderObject() : shader_object_(0) {}

//! Constructor
/*!
 @param source    A string holding either the source code of the shader object,
                  or a path to a file which contains this source code, depending
				  on the from_file flag
 @param type      The type of shader object to define. This parameter can either
                  be vertex, geometry or fragment shader
 @param from_file If set to true, the parameters source should contain a path to
                  a text file which contains the shader object source code. Otherwise,
				  parameter source should contain the source code itself
 */
GLSLShaderObject::GLSLShaderObject(const char* source, Type type, bool from_file) : shader_object_(0) {
	set_shader(source, type, from_file);
}

//! Copy constructor
/*!
 @param rhs The shader object to copy
 */
GLSLShaderObject::GLSLShaderObject(const GLSLShaderObject& rhs) : shader_object_(rhs.shader_object_), GraphicResource() {
	if ( shader_object_ ) ResourceManager<ID,CONTEXTID>::instance()->reference_resource(*this);
}

//! Destructor
GLSLShaderObject::~GLSLShaderObject() {
	clean_id();
}

//! Assignment operator
/*!
 @param rhs The shader object to copy
 */
GLSLShaderObject& GLSLShaderObject::operator=(const GLSLShaderObject& rhs) {
	if ( &rhs != this ) {
		ResourceManager<ID,CONTEXTID>* manager = (ResourceManager<ID,CONTEXTID>*)ResourceManager<ID,CONTEXTID>::instance();
		if ( shader_object_ ) manager->unreference_resource(*this);
		shader_object_ = rhs.shader_object_;
		if ( shader_object_ ) manager->reference_resource(*this);
	}
	return *this;
}

//! Test if the two shader objects share the same shader object ID
/*!
 @param rhs The shader object to test
 @return True if the two shader objects share the same shader object ID, false otherwise
 */
bool GLSLShaderObject::operator==(const GLSLShaderObject& rhs) const {
	return ( shader_object_ == rhs.shader_object_ );
}

//! Test if the two shader objects have a different shader object ID
/*!
 @param rhs The shader object to test
 @return True if the two shader objects have a different shader object ID, false otherwise
 */
bool GLSLShaderObject::operator!=(const GLSLShaderObject& rhs) const {
	return ( shader_object_ != rhs.shader_object_ );
}

//! Test if the rhs shader object has an inferior shader object ID
/*!
 *  This function is defined for compatibility of DLL exportations
 *
 @param rhs The shader object to compare
 @return True if the rhs shader object has an inferior shader object ID, false otherwise
 */
bool GLSLShaderObject::operator<(const GLSLShaderObject& rhs) const {
	return ( shader_object_ < rhs.shader_object_ );
}

//! Set the shader object source code
/*!
 @param source    A string holding either the source code of the shader object,
                  or a path to a file which contains this source code, depending
				  on the from_file flag
 @param type      The type of shader object to define. This parameter can either
                  be vertex, geometry or fragment shader
 @param from_file If set to true, the parameters source should contain a path to
                  a text file which contains the shader object source code. Otherwise,
				  parameter source should contain the source code itself
 @param compile_immediately If set to true (the default), the shader object is compiled as soon as its
                            source has been defined. Otherwise, compilation has to be done manually using
							the compile() function.
 */
bool GLSLShaderObject::set_shader(const char* source, Type type, bool from_file, bool compile_immediately) {
	if ( source == nil ) return false;

    // Get source code
	const char* theSource = nil;

	// Get vertex shader source code
	GEString sourceStr;
	if ( from_file ) {
		sourceStr = load_file(source);
		if ( sourceStr.empty() ) return false;
		theSource = sourceStr.c_str();
	}
	else theSource = source;
	if ( theSource == nil ) return false;

	// Create and compile the shader
	if ( shader_object_ == 0 ) create_id(type);
	glShaderSource(shader_object_, 1, &theSource, nil);
	if ( compile_immediately == false ) return true;
	return compile();
}

//! Try to compile the shader object
/*!
 @return True if compilation succeeded, false otherwise
 */
bool GLSLShaderObject::compile() const {
	// Try to compile the shader
	if ( shader_object_ == 0 ) return false;
	glCompileShader(shader_object_);

	// Check compilation
	return check_compiling("Shader error - cannot compile.\n");
}

//! Check whether the shader was correctly compiled
/*!
 @param message The message to be output in case of error
 @return True if compilation was successful, false otherwise
 */
bool GLSLShaderObject::check_compiling(const char* message) const {
	// Check compile status
    int success;
    glGetShaderiv(shader_object_, GL_COMPILE_STATUS, &success);
	if ( success != GL_FALSE ) return true;

	// Print error message in case of failure
	std::cerr << message << std::endl;

	// Get and print error log
	int infoLogSize;
	glGetShaderiv(shader_object_, GL_INFO_LOG_LENGTH, &infoLogSize);
	char* buffer = MemoryManager::instance()->allocate<char>(infoLogSize);
	glGetShaderInfoLog(shader_object_, infoLogSize, NULL, buffer);
	std::cerr << buffer << std::endl;
	MemoryManager::instance()->desallocate(buffer,infoLogSize);

	// Return failure
	return false;
}

//! Loads a shader source code file from disk
/*!
 @param Filename The path to the file that should be read
 @return A string containing the content of the file
 */
GEString GLSLShaderObject::load_file(const char* filename) {
	// Get the file
	std::ifstream stream(filename, std::ios::in);

	// Check if we were able to open the file
    if(!stream.is_open())
        return "";

	// Read the file
    GEString line = "", text = "";
	while(getline(stream, line))
    	text += "\n" + line;
	stream.close();

	// Return the content of the file
	return text;
}

//! Clean the previous ID
void GLSLShaderObject::clean_id() {
	// Nothing to do if ID is not allocated
	if ( shader_object_ == 0 ) return;

	// Unreference ID from the resource manager
	ResourceManager<ID,CONTEXTID>::instance()->unreference_resource(*this);

	// Clean ID member variable
	shader_object_ = 0;
}

//! Allocate a new ID
/*!
 @param type The type of shader object that should be created (vertex, geometry or fragment shader)
 */
void GLSLShaderObject::create_id(Type type) {
	// Clean previous ID, if any
	clean_id();

	// Generate a new shader object ID
	shader_object_ = glCreateShader(type);

	// Reference this ID
	ResourceManager<ID,CONTEXTID>::instance()->reference_resource(*this);
}

//! Desallocate the shader object ID
void GLSLShaderObject::destroy_resource() const {
	glDeleteShader(shader_object_);
}

//! Return the shader object context
GLSLShaderObject::CONTEXTID GLSLShaderObject::context() const {
	return rtti_id_;
}

//! Return the shader object reference
GLSLShaderObject::ID GLSLShaderObject::reference() const {
	return shader_object_;
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
